package com.h2cloud.spring;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.*;

public class PropertyPlaceholderHelper {

    private static final Log logger = LogFactory.getLog(PropertyPlaceholderHelper.class);

    private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);

    static {
        wellKnownSimplePrefixes.put("}", "{");
        wellKnownSimplePrefixes.put("]", "[");
        wellKnownSimplePrefixes.put(")", "(");
    }


    private final String placeholderPrefix;

    private final String placeholderSuffix;

    private final String simplePrefix;

    private final String valueSeparator;

    private final boolean ignoreUnresolvablePlaceholders;


    public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
        this(placeholderPrefix, placeholderSuffix, null, true);
    }

    /**
     * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix.
     *
     * @param placeholderPrefix              the prefix that denotes the start of a placeholder
     * @param placeholderSuffix              the suffix that denotes the end of a placeholder
     * @param valueSeparator                 the separating character between the placeholder variable
     *                                       and the associated default value, if any
     * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should
     *                                       be ignored ({@code true}) or cause an exception ({@code false})
     */
    public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
                                     String valueSeparator, boolean ignoreUnresolvablePlaceholders) {

        this.placeholderPrefix = placeholderPrefix;
        this.placeholderSuffix = placeholderSuffix;
        String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
        if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
            this.simplePrefix = simplePrefixForSuffix;
        } else {
            this.simplePrefix = this.placeholderPrefix;
        }
        this.valueSeparator = valueSeparator;
        this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
    }


    /**
     * Replaces all placeholders of format {@code ${name}} with the corresponding
     * property from the supplied {@link Properties}.
     *
     * @param value      the value containing the placeholders to be replaced
     * @param properties the {@code Properties} to use for replacement
     * @return the supplied value with placeholders replaced inline
     */
    public String replacePlaceholders(String value, final Properties properties) {
        return replacePlaceholders(value, new PlaceholderResolver() {
            public String resolvePlaceholder(String placeholderName) {
                return properties.getProperty(placeholderName);
            }
        });
    }

    /**
     * Replaces all placeholders of format {@code ${name}} with the value returned
     * from the supplied {@link PlaceholderResolver}.
     *
     * @param value               the value containing the placeholders to be replaced
     * @param placeholderResolver the {@code PlaceholderResolver} to use for replacement
     * @return the supplied value with placeholders replaced inline
     */
    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
        return parseStringValue(value, placeholderResolver, new HashSet<String>());
    }

    protected String parseStringValue(
            String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

        // 查找value中第一个占位符前缀
        int startIndex = value.indexOf(this.placeholderPrefix);
        // 如果没有占位符前缀, 说明value中不包含占位符, 无需替换, 直接返回
        if (startIndex == -1) {
            return value;
        }

        StringBuilder result = new StringBuilder(value);
        while (startIndex != -1) {
            // 关键, startIndex表示最外层占位符前缀索引, endIndex表示最外层占位符后缀索引
            int endIndex = findPlaceholderEndIndex(result, startIndex);
            if (endIndex != -1) {
                // placeholder表示最外层占位符名称, 可能嵌套有占位符
                String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                String originalPlaceholder = placeholder;
                if (visitedPlaceholders == null) {
                    visitedPlaceholders = new HashSet<>(4);
                }
                // 防止占位符循环引用
                if (!visitedPlaceholders.add(originalPlaceholder)) {
                    throw new IllegalArgumentException(
                            "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
                }
                // 解析最外层占位符之前先要解析嵌套占位符, 这里递归解析最外层占位符名称中内部嵌套占位符
                placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                // 嵌套占位符解析完成后, 占位符名称placeholder中就不包含占位符, 可以直接从placeholderResolver获取替换值
                String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                // 有默认值情况
                if (propVal == null && this.valueSeparator != null) {
                    int separatorIndex = placeholder.indexOf(this.valueSeparator);
                    if (separatorIndex != -1) {
                        // 如果包含默认值, 则placeholder中包含默认值分隔符和默认值;
                        // 例如对于${app.name:name}, placeholder为app.name:name, 上面代码会解析失败;
                        // 这样做的好处是,如果默认值中包含占位符,则属于嵌套占位符,上面递归解析会将其一起解析;

                        // 这里需要拿到实际占位符名称再次解析,actualPlaceholder即为实际占位符名称, 例子中值为app.name
                        String actualPlaceholder = placeholder.substring(0, separatorIndex);
                        // 获取默认值, 例如${app.name:name}默认值为name
                        String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                        propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                        // 无替换值, 取默认值
                        if (propVal == null) {
                            propVal = defaultValue;
                        }
                    }
                }
                if (propVal != null) {
                    // 替换值可能还包含占位符, 还需要递归调用再次解析
                    // 例如, ${app.key}将app.key解析成${user.home}后, 该字符串仍然包含占位符, 需要再次解析
                    propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                    result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Resolved placeholder '" + placeholder + "'");
                    }
                    // 继续解析下一个最外层占位符
                    // 例如${app.name}+${user.home},
                    // 上面的程序只是把${app.name}解析完成, ${user.home}需要通过while循环继续解析
                    startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
                }
                // 忽略解析失败占位符, 继续解析下一个占位符
                else if (this.ignoreUnresolvablePlaceholders) {
                    startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
                }
                // 不能忽略, 则报错
                else {
                    throw new IllegalArgumentException("Could not resolve placeholder '" +
                            placeholder + "'" + " in value \"" + value + "\"");
                }
                visitedPlaceholders.remove(originalPlaceholder);
            } else {
                startIndex = -1;
            }
        }
        return result.toString();
    }

    // ${${user.home}}
    private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
        //获取前缀后面一个字符的索引
        int index = startIndex + this.placeholderPrefix.length();
        int withinNestedPlaceholder = 0;
        //如果前缀后面还有字符的话
        while (index < buf.length()) {
            //判断源字符串在index处是否与后缀匹配
            if (substringMatch(buf, index, this.placeholderSuffix)) {
                //如果匹配到后缀,但此时前缀数量>后缀,则继续匹配后缀
                if (withinNestedPlaceholder > 0) {
                    withinNestedPlaceholder--;
                    index = index + this.placeholderSuffix.length();
                } else {
                    return index;
                }
            } else if (substringMatch(buf, index, this.simplePrefix)) {
                //判断源字符串在index处是否与前缀匹配,若匹配,说明前缀后面还是前缀,则把前缀长度累加到index上,继续循环寻找后缀
                //withinNestedPlaceholder确保前缀和后缀成对出现后
                withinNestedPlaceholder++;
                index = index + this.simplePrefix.length();
            } else {
                //如果index处既不能和suffix又不能和simplePrefix匹配,则自增,继续循环
                index++;
            }
        }
        return -1;
    }

    // 下面这个方法是用来
    private boolean substringMatch(CharSequence str, int index, CharSequence substring) {
        for (int j = 0; j < substring.length(); j++) {
            int i = index + j;
            if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Strategy interface used to resolve replacement values for placeholders contained in Strings.
     */
    public static interface PlaceholderResolver {

        /**
         * Resolve the supplied placeholder name to the replacement value.
         *
         * @param placeholderName the name of the placeholder to resolve
         * @return the replacement value, or {@code null} if no replacement is to be made
         */
        String resolvePlaceholder(String placeholderName);
    }

    // 占位符由${name:defaultValue}组成，一般来说在属性表中找到名称为name的属性，则返回属性值，否则返回冒号（:）后的默认值
    public static void main(String[] args) throws Exception {
//        InputStream in = new BufferedInputStream(new FileInputStream("/Users/a58/javaProject/myspringboot/src/test/java/com/h2cloud/spring/test01.properties"));
//        Properties properties = new Properties();
//        properties.load(in);
//
//        PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${", "}", ":", true);
//        String resolved = helper.replacePlaceholders("port:${${application.name}.port:9000}", placeholderName -> {
//            String value = properties.getProperty(placeholderName);
//            return value;
//        });
//        System.out.println("resolved = " + resolved);

        PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${", "}", ":", false);
        Map<String, String> map = new HashMap<>();
        map.put("app.name", "fsx");
        map.put("user.home", "app.name");
        map.put("app.key", "${user.home}");

//        // 占位符的值由map提供, 输出fsx
//        System.err.println(helper.replacePlaceholders("${app.name}", map::get));
//
//        // 输出fsx, 支持嵌套
        System.err.println(helper.replacePlaceholders("${{user.home}}", map::get));

        // 输出fsx+app.name, 与c语言printf类似,replacePlaceholders只替换占位符的值，其余字符原封不动输出
        System.err.println(helper.replacePlaceholders("${app.name}+${user.home}", map::get));

        // 输出app.name, 支持递归解析
        System.err.println(helper.replacePlaceholders("${app.key}", map::get));

        // 输出${app.user}, map中不包含app.user的值但ignoreUnresolvablePlaceholders为true, 对不能解析的占位符不做处理
        System.err.println(new PropertyPlaceholderHelper("${", "}", ":", true).replacePlaceholders("${app.user}", map::get));

        // 报错, map中不包含app.user的值且ignoreUnresolvablePlaceholders为false
        System.err.println(helper.replacePlaceholders("${app.user}", map::get));
    }

}

